Skip to main content

Using Github Action to Automatically Deploy Websites/Backend Applications

As the number of projects I write increases, so too does the demand for deployment.

Previously, the method I used to update deployments was to write a Bash script to semi-automatically update the project:

Whenever the project had new changes completed, for front-end projects, the build script would run automatically, and then upload to the remote server (which has been configured with Nginx settings) using the scp tool. For backend projects, the application would be automatically containerized, and then I would still need to ssh to the server background to manually run the docker-compose.yml file to pull and run the new image.

In short, every time new changes were pushed to the GitHub repository, I had to manually run this Bash script. Finally, I recently decided to fully automate this process!

Because I have been using Vercel to deploy ChatGPT-Next-Web as my AI chat assistant recently, I found that every time a new commit was submitted to this repository, my Vercel application would automatically update. On this occasion, I learned about Github Action.

After some exploration and failures, I finally succeeded in setting all my projects to fully automated deployment with Github Action.

Here, I will record a typical Workflow setting for future reference:

name: CI/CD Pipeline

on:
  push:
    branches:
      - main

jobs:
  check-skip-ci:
    runs-on: ubuntu-latest
    outputs:
      skip: ${{ steps.check.outputs.skip }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Check for [skip ci] in commit message
        id: check
        run: |
          if [[ "${{ github.event.head_commit.message }}" == *"[skip ci]"* ]]; then
            echo "::set-output name=skip::true"
          else
            echo "::set-output name=skip::false"
          fi          

  build-and-deploy:
    needs: check-skip-ci
    if: needs.check-skip-ci.outputs.skip == 'false'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20.12.2"

      - name: Install TypeScript
        run: npm install -g typescript@4.9.3

      - name: Install pnpm
        run: npm install -g pnpm

      - name: Install dependencies
        run: pnpm install

      - name: Build the project
        run: pnpm run build

      - name: Log in to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          tags: ${{ secrets.DOCKER_USERNAME }}/bookkeeper:latest
          push: true

      - name: Deploy to remote server
        env:
          HOST: ${{ secrets.REMOTE_HOST }}
          USER: ${{ secrets.REMOTE_USER }}
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        run: |
          echo "$SSH_PRIVATE_KEY" > private_key
          chmod 600 private_key
          ssh -i private_key -o StrictHostKeyChecking=no $USER@$HOST 'docker compose -f /root/www/bkp/docker-compose.yml down || true && sudo rm -f /root/www/bkp/docker-compose.yml'
          scp -i private_key -o StrictHostKeyChecking=no docker-compose.yml $USER@$HOST:/root/www/bkp/docker-compose.yml
          ssh -i private_key -o StrictHostKeyChecking=no $USER@$HOST <<EOF
            docker compose -f /root/www/bkp/docker-compose.yml pull
            docker compose -f /root/www/bkp/docker-compose.yml up -d
            docker system prune -f
          EOF
          rm -rf private_key          

In this settings file, I added two jobs, one is check-skip-ci, and the other is build-and-deploy.

The purpose of check-skip-ci is to check the commit message for the phrase [skip ci] when a new commit is pushed to the Github repository. If it is found, it skips build-and-deploy. The purpose of writing this job is that there may be times when I just make changes to the README.md file, and in these cases, I wouldn’t want my project to be rebuilt and redeployed. I can add [skip ci] at the beginning of the commit message to skip this process.

Finally, the method and location for adding environment variables are shown in the figure.